#!/usr/bin/python

#GUI imports
from Tkinter import *
import tkFileDialog
import tkMessageBox
#XML imports
from xml.sax import make_parser
from xml.sax.handler import ContentHandler
#Other
import sys,os,traceback
import getopt
# My stuff
from IO import MapParse, XML_Out
from newmap import NewMap

###########################
# Globals
###########################

help = """
   C 2006 Henry Finucane and TC Productions.
   "We do useless!"
   """

debuglevel = 0

###########################
# Misc Methods
###########################


def debug(message,level=10):
	if debuglevel >= level:
		sys.stderr.write(message + "\n")

def main():
	args = sys.argv[1:]
	try:
		optlist, args = getopt.getopt(args,"hd:v",["help","debug="])
	except getopt.GetoptError:
		print "Wrong usage"
		sys.exit(getopt.GetoptError)
		
	#~ print optlist
	#~ print args
	for switch in optlist:
		if switch[0] == "-h" or switch[0] == "--help":
			print help
		if switch[0] == "-d" or switch[0] == "--debug":
			print "Your debug level is: ", switch[1]
			debuglevel = switch[1]
		
def DrawMap(sourcemap,targetcanvas,imlist):
	"""
	This method does exactly what it says it does, it draws the map.
	
	You give it a source object, a canvas to blit to, and a dictionary of images to use, and it's happy.
	
	This should only be called directly when there is a new map being drawn, otherwise stick to ChangeMap,
	too much boilerplate code was getting written.
	"""
	#This method basically assumes all your tiles are squares. Don't mess with it.
	# It uses desert to test, too. Um.... very ugly. Yeah.
	size = imlist["desert"].height() #imlist == list of images used as textures. Tiles + units
	targetcanvas.delete(ALL) # Clear the area!
	debug(str(imlist) + " \n IMLIST \n ")
	#Draw terrain
	for i in range(0,sourcemap.getColumnNum()):
		for j in range(0,sourcemap.getRowNum()):
			#Longest line ever. I should split the tag bit off to make it more clear.
			targetcanvas.create_image((i*size),(j*size),image=imlist[sourcemap.getTerrain(i,j)],\
			tags=(i,j,sourcemap.getTerrain(i,j),sourcemap.getUnit(i,j)))
			#Note that the unit on a square is a value of the terrain, nothing else.
			#~ debug("created rectangle at" + str(i)+ "," + str(j))
			
	#Now draw the units
	for i in range(0,sourcemap.getColumnNum()):
		for j in range(0,sourcemap.getRowNum()):
			if sourcemap.getUnit(i,j):
				unit = sourcemap.getUnit(i,j)
				unit = unit[:-1]
				try:
					targetcanvas.create_image((i*size),(j*size),image=imlist[unit], tags=(i,j,sourcemap.getUnit(i,j)))
				except KeyError:
					debug(str(KeyError))
				#~ debug("drew a unit at " + str(i) + "," + str(j))
	
	return targetcanvas
	
def drawPalette(canvas,images,selected=None):
	canvas.delete(ALL)
	for i in images:
		canvas["height"] = str(int(canvas["height"])+50)
		debug(str(i)+" <-things passed that are missing unit names\n")
		try:
			canvas.create_image(25,int(canvas["height"])-25, image=i[1],tags=(i[0]))
			if i[0] == selected:
				canvas.create_rectangle(0,int(canvas["height"])-50,50,int(canvas["height"]),outline="red",width="5",tag="highlighter")
		except:
			canvas.create_image(25,int(canvas["height"])-25,image=i)
			if i == selected:
				canvas.create_rectangle(0,int(canvas["height"])-50,50,int(canvas["height"]),outline="red",width="5",tag="highlighter")
		
	return canvas
	
def ChangeMap(sourcemap,targetcanvas,imlist,**targets):
	"""
	This is a wierd little method. The first three arguments, sourcemap, targetcanvas, imlist, are the 
	same as DrawMap. Pass what you get from MapParse to that. `targetcanvas' is your output, and
	it needs to be a Tkinter canvas object. 'imlist' is a dictionary of images.
	
	Now, to the new stuff. You need to use keywords, and if you're moving multiple units or whatever,
	you need to pass tuples of tuples
	
	***Note on units: to delete a unit, pass "" as the unit name.***
	
	*units should be passed as a tuple of tuples. Sort of. Anyway, each `unit' is should be:
	(xCoordinate,yCoordinate,UnitName)
	*tiles is similiar:
	(xCoordinate,yCoordinate,TerrainType)
	
	Example:   
	ChangeMap(self.currentmap,self.drawingArea,self.imageDictionary,units=((1,1,"archer1"),(2,20,"melee2")),tiles=(1,1,"grass"))
	
	If you pass a blank name for terrain, I think the universe explodes.
	
	Note that, for stupid reasons, the team number is appended to the unit name. Get over it.
	"""
	if targets.has_key("units"):
		if type(targets["units"][0]) == type([]):
			for i in targets["units"]:
				debug("i is "+str(i))
				sourcemap.setUnit(i[0],i[1],i[2])
		else:
			sourcemap.setUnit(targets["units"][0],targets["units"][1],targets["units"][2])
			
	if targets.has_key("tiles"):
		debug(str(targets["tiles"]))
		if type(targets["tiles"][0]) == type([]):
			for j in targets["tiles"]:
				debug("j is "+str(j))
				sourcemap.setTerrain(j[0],j[1],j[2])
		else:
			sourcemap.setTerrain(targets["tiles"][0],targets["tiles"][1],targets["tiles"][2])
	# I love functions
	return DrawMap(sourcemap,targetcanvas,imlist)
	# That was easy :)


##
## The following needs to be refactored
##
class Editor:
	def __init__(self,parent):
		self.parent = parent
		self.top = Frame(parent,background="white")
		self.parent.geometry('640x580+100+60') #Magic number means: 640x480, offset (100,60)
		self.top.pack(fill=BOTH,expand=YES) #Makes the window resize properly

                #Constants:
                button_padX = 2
                button_padY = 1
                frame_padX = 3
                frame_padY = 2
                #End constants

                #Frames
		self.menubar = Frame(self.top)
		self.menubar.pack(side=TOP,padx=frame_padX,pady=frame_padY)

 		self.activeArea = Frame(self.top,borderwidth=5,relief=RIDGE,background="tan")
		self.activeArea.pack(side=RIGHT,fill=BOTH,expand=YES)
		
                self.paletteBar = Frame(self.top,width=50,background="grey",height=500,\
		borderwidth=5,relief=RIDGE)
                self.paletteBar.pack(side=LEFT,padx=frame_padX,pady=frame_padY)
                
                self.terrainFrame = Frame(self.paletteBar,background="red",width=150)
                self.terrainFrame.pack(fill=X,padx=3,pady=1)
		
		#Scrollers
		self.mapScrollBarY = Scrollbar(self.activeArea,orient=VERTICAL)
		self.mapScrollBarX = Scrollbar(self.activeArea,orient=HORIZONTAL)
                self.mapScrollBarY.pack(side=RIGHT,fill=Y)
                self.mapScrollBarX.pack(side=BOTTOM,fill=X)

		
                #Buttons
		self.quitbutton = Button(self.menubar,text="Quit",activebackground="red")
		self.quitbutton.bind("<ButtonRelease>",self.Quit)
                self.quitbutton.configure(padx=button_padX,pady=button_padY)
		self.quitbutton.pack(side="right")

		self.openbutton = Button(self.menubar, text="Open",activebackground="red")
		self.openbutton.bind("<ButtonRelease>",self.Open)
                self.openbutton.configure(padx=button_padX,pady=button_padY)
		self.openbutton.pack(side="left")

		self.savebutton = Button(self.menubar,text="Save",activebackground="red")
		self.savebutton.bind("<ButtonRelease>",self.SaveNow)
		self.savebutton.configure(padx=button_padX,pady=button_padY)
		self.savebutton.pack(side="left")
		
		self.newbutton = Button(self.menubar,text="New",activebackground="red")
		self.newbutton.bind("<ButtonRelease>",self.NewMap)
		self.newbutton.configure(padx=button_padX,pady=button_padY)
		self.newbutton.pack(side="right")

		######################
		# Bullshit section that needs SERIOUS CLEANUP
		# Ostensibly it should make the various image dictionaries that are floating around
		# but it's inefficient and duplicates work.
		######################
		#Tile stuff
		self.currentTerrain = "grass" # Default paint texture
		self.currentUnit = "melee"
                # Palettes
                self.terrainScreen = Canvas(master=self.terrainFrame,width=50,height=0) # terrain display
		#~ self.unitScreen = Canvas(master=self.terrainFrame,width=50,height=0) # deprecated unit display
		self.unitImages = os.listdir('./icons/units/') # unit icons for the palette
		self.unitImages = filter(lambda x: x.find('.gif') > 0 , self.unitImages)
		self.unitImages = [(image[:-4],PhotoImage(file="icons/units/"+image)) for image in self.unitImages]
                # Load big palettes
		self.terrainImages = os.listdir('./icons/terrain') # terrain icons for palette
		self.terrainImages = filter(lambda x: x.find('.gif') > 0 , self.terrainImages)

		self.terrainImages = [(i[:-4],PhotoImage(file="icons/terrain/"+i)) for i in self.terrainImages]
		#
		self.terrainThumbs = os.listdir('./icons/terrainThumbs') # smaller terrain icons for display
		#Strip the list of extensions & prefixes.
		self.terrainThumbs = [i[6:-4] for i in self.terrainThumbs]
		self.terrainThumbs = filter(lambda x: len(x), self.terrainThumbs)
		
		#The following should, perhaps, be isolated from the rest of __init__ for perty.
		self.imageDictionary = {} # Stores the images to be used on the main display
		# Add terrain
		for a in self.terrainThumbs:
			if not self.imageDictionary.has_key(a):
				self.imageDictionary[a] = PhotoImage(file="icons/terrainThumbs/thumb-"+a+".gif")
			else:
				debug("Error in supplied terrain gifs. Check icons/terrainThumbs/" + a,4)
		# Add units
		debug(str(self.unitImages))
		for a in self.unitImages:
			if not self.imageDictionary.has_key(a[0]):
				self.imageDictionary[a[0]] = PhotoImage(file="icons/units/"+a[0]+".gif")
			else:
				debug("your mother ate your unitImages" + a,4)
		
		self.terrainImages += self.unitImages
		self.terrainImages += ("",PhotoImage(file="./icons/delete.gif"))
		self.terrainScreen = drawPalette(self.terrainScreen,self.terrainImages,selected="grass")
		#~ self.unitScreen = drawPalette(self.unitScreen,self.unitImages,palettetype="unit") # deprecated

		debug("height of terrainScreen "+self.terrainScreen["height"],5)
	
		#~ self.unitScreen.bind("<Button-1>",self.changeUnit)
		#~ self.unitScreen.pack(fill=BOTH,expand=YES)

                self.terrainScreen.bind("<Button-1>", self.changeBrush)
		self.terrainScreen.pack(fill=BOTH,expand=YES)
	
		# Class constants
		self.oldx = 0
		self.oldy = 0
		self.Team = 1
		self.brush = 0

		self.teamButton = Button(text="Team "+str(self.Team),command=self.setTeam)
		self.teamButton.pack()
		
	#######################################
	# Now for methods. First, the methods for the buttons up top:
	#######################################

	def Open(self,event=None,target=None):
		#Get map:
		if not target:
			target = tkFileDialog.askopenfilename()
		if not target:
			return
		try:
			self.currentmap = MapParse(target)
		except:
			tkMessageBox.showerror("File error","Malformed XML file, sorry. Specific error: " + str(sys.exc_type))
			traceback.print_exc(sys.stderr)
			debug(str(sys.exc_info()[0]),4)
			pass
		
		#print str(self.currentmap)
		# Create the canvas
		try:
			self.drawingArea.pack_forget()
			self.drawingArea.delete(ALL)
		except:
			pass
		self.drawingArea = Canvas(self.activeArea,\
		xscrollcommand=self.mapScrollBarX.set,yscrollcommand=self.mapScrollBarY.set)


		self.mapScrollBarY.config(command=self.drawingArea.yview)
                self.mapScrollBarX.config(command=self.drawingArea.xview)
		#Fix canvas alignment:
		self.drawingArea.xview_moveto(0)
		self.drawingArea.yview_moveto(0)

		# Draw the sucker
		self.drawingArea = DrawMap(self.currentmap,self.drawingArea,imlist=self.imageDictionary)
                debug(str(self.drawingArea["height"])+" height of drawingArea",5)

                #self.drawingArea.config(scrollregion = (0,0,self.drawingArea["width"],self.drawingArea["height"]))
                self.drawingArea.config(scrollregion=self.drawingArea.bbox(ALL))

		self.drawingArea.bind("<ButtonPress-1>",self.startPaint)
		self.drawingArea.bind("<Motion>",self.paintMap)
		self.drawingArea.bind("<ButtonRelease-1>",self.stopPaint)
		
		self.drawingArea.pack(fill=BOTH,expand=YES)
		self.draw = False
		
		
	def NewMap(self,event):
		#Save state.
		try:
			self.tmpfile = open('swap','w')
			XML_Out(self.currentmap,self.tmpfile)
			self.tmpfile.close()
		except:
			tkMessageBox.showinfo(title="Failed",message=("No map was created. This error may be spurious. Error: "+  str(sys.exc_info()[0])))
			traceback.print_exc(sys.stderr)
			# The problem with this is that if there is an actual problem, like swap being read-only,
			# the user doesn't know. Right now this just makes sure that currentmap exists :/
			pass
		#Open dialog
		try:
			NewMapDialog = NewMap(self.parent)
			self.parent.wait_window(NewMapDialog.top)
		# Draw the ugly mofo
			swapfile = open('swap','r')
			self.Open(target=swapfile)
		except:
			tkMessageBox.showinfo(title="Failed",message=("Couldn't read the swapfile. Error: "+ str(sys.exc_info()[0])))
			traceback.print_exc(sys.stderr)
			pass
			
	def SaveNow(self,event):
		target = tkFileDialog.asksaveasfile()
		if not target:
			return
		#~ print "target is: " + str(target)
		XML_Out(self.currentmap,target)
		tkMessageBox.showinfo(title="File saved",message=("Your map was saved as "+str(target.name)))

	def Quit(self,event):
		try:
			os.remove('swap')
		except:
			pass
		raise SystemExit
	
	#####################
	# This is the button down below
	#####################
	def setTeam(self):
		if self.Team is not 1:
			self.Team = 1
		else:
			self.Team = 2
		self.teamButton["text"] = "Team " + str(self.Team)

	#############################
	# These are methods used by the big canvas
	#############################
	def paint(self,X,Y):
		"""
		Paints a unit or terrain texture at the given point
		"""
		
		# Nice and short :)
		debug(str(self.brush) + " <- brush #")
		if self.brush < 5:
			debug("brush is terrain")
			self.drawingArea = ChangeMap(self.currentmap,self.drawingArea,self.imageDictionary, tiles=(X,Y,self.currentTerrain))
		else:
			debug("brush is unit. currentunit = "+self.currentUnit + " team is " + str(self.Team))
			UNIT = self.currentUnit + str(self.Team)
			debug("UNIT = " + UNIT)
			self.drawingArea = ChangeMap(self.currentmap,self.drawingArea,self.imageDictionary, units=(X,Y,UNIT))
			
	def startPaint(self,event):
		# Set vars
		self.draw = True
		self.drawingArea.config(cursor="spraycan")
		id= self.drawingArea.gettags("current")
		try:
			tilex,tiley = int(id[0]), int(id[1])
		except IndexError:
			debug("Either you clicked off the map or DrawMap is b0rked again.")
			debug("Here's the error:\n\n"+str(IndexError)+"\n\n")
			return
		self.oldx, self.oldy = tilex, tiley
		self.paint(tilex,tiley)
		
	def paintMap(self,event):
		if self.draw:
			#The limitation of the following method is that the units are bigger than the tiles, so if you click on a sticky-outy bit, it draws on the tile that the unit belongs too. Not a biggy, I should probably shrink the unit size anyhow, it looks silly in the status quo.
			id = self.drawingArea.find_closest(self.drawingArea.canvasx(event.x),self.drawingArea.canvasy(event.y))
			self.drawingArea.config(cursor="spraycan")
			#~ test = self.drawingArea.gettags("current")
			# Doesn't work because 'current' only occurs on the click, NOT on the drag.
			# Wierd.
			#~ print id,"+++",current
			tilex,tiley = int(self.drawingArea.gettags(id)[0]), int(self.drawingArea.gettags(id)[1])
			self.oldx, self.oldy = tilex, tiley
			self.paint(tilex,tiley)	
		
	def stopPaint(self,event):
		try:
			self.drawingArea.config(cursor="arrow")
		except:
			debug("you're unlucky, you're Tk install is b0rked",4)
		self.draw = False
		
	
	###########################
	# These are methods used by the palettes. 
	###########################

		
	def changeBrush(self,event):
		"""
		This changes the type of thing that is to be painted.
		"""
		if self.terrainScreen.find_withtag(CURRENT):
			self.terrainScreen.delete("highlighter")
			xco = event.x/50
			yco = event.y/50
			self.brush = yco
			
			if self.brush < 5:
				self.currentTerrain = self.terrainScreen.gettags(CURRENT)[0]
			else:
				self.currentUnit =self.terrainScreen.gettags(CURRENT)[0]
				debug("self.currentUnit changed to: " + self.currentUnit)
			self.terrainScreen.create_rectangle(xco*50,yco*50,xco*50+50,yco*50+50,outline="red",width=5,tag="highlighter")
			# The divide by 50, multiply by 50 routine is done to round it to the nearest 50.
			# Ugly, I know. It does seem to work though.

	def changeUnit(self,event):
		unitdict = {1:"archer",2:"siege",3:"melee"}
		newUnit = unitdict[self.unitScreen.find_withtag(CURRENT)[0]]
		unit = self.currentmap.getUnit(self.oldx,self.oldy)
		debug(str( self.oldx ) + str( self.oldy ) + "oldx oldy" , 4 )
		if unit is not 0:
			if newUnit is not unit[:-1]:
				self.currentmap.setUnit(self.oldx,self.oldy,newUnit+str(self.Team))
				self.drawingArea.delete(ALL)
				self.drawingArea = DrawMap(self.currentmap,self.drawingArea,self.imageDictionary)
		else:
			self.currentmap.setUnit(self.oldx,self.oldy,newUnit+str(self.Team))
			self.drawingArea.delete(ALL)
			self.drawingArea = DrawMap(self.currentmap,self.drawingArea,self.imageDictionary)
		

if __name__ == "__main__":
	main()
	debug("Begun")
	rootWindow = Tk()
	TheMapEditor = Editor(rootWindow)
	rootWindow.mainloop()
